﻿//==============================================================================  
//Copyright (C) 2012-2015 9UN.ORG. All rights reserved. 
//GUID：46172595-6b69-4526-b9da-426d663f5b05
//CLR Version: 4.0.30319.18033
//Code Author：Kevin Wang
//Contact：Email(Admin@9un.org),QQ(265382 or 74344)
//Filename：MediaLibraryFactory
//Namespace：RuanRang.Media.Core
//Functions：MediaLibraryFactory  
//Created by Kevin Wang at 2013/2/2 23:05:09 http://blog.9un.org
//============================================================================== 

#region Usings

using System;
using System.Runtime.InteropServices;
using RuanRang.Media.Core.Common;
using RuanRang.Media.Core.Exceptions;
using RuanRang.Media.Core.Internal.DataStructures;
using RuanRang.Media.Core.Internal.Interop;
using RuanRang.Media.Core.Io;
using RuanRang.Media.Core.Media;
using log4net;

#endregion

namespace RuanRang.Media.Core
{
    /// <summary>
    /// Implements <see cref="ILibVlcFactory"/> and manages vlc specific objects internally.
    /// </summary>
    public class LibVlcFactory : IDisposable, ILibVlcFactory
    {
        /// <summary>
        /// Associated logger. Used primarily to log vlclib logging messages.
        /// </summary>
        private readonly ILog _logger;
        public const string DefaultLoggerClassName = "LibVlcFactory";
        private readonly IntPtr _vlcLibHandle;
        //private readonly InternalObjectsFactory _internalFactory;
        //private readonly VlcLog _vlcLog;
        //private Thread _thread;
        //private readonly EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
        //private bool _threadStopSignalSent;
        /// <summary>
        /// Instantiates vlc library using specific parameters.
        /// </summary>
        /// <param name="parameters">Parameters for vlc core.</param>
        public LibVlcFactory(string[] parameters)
            : this(parameters, DefaultLoggerClassName, true)
        {

        }

        /// <summary>
        /// Instantiates vlc library using specific parameters and logger class name.
        /// </summary>
        /// <param name="parameters">Parameters for vlc core.</param>
        /// <param name="loggerClassName">Name of logger to use for vlc logs.</param>
        public LibVlcFactory(string[] parameters, string loggerClassName)
            : this(parameters, loggerClassName, true)
        {

        }

        /// <summary>
        /// Instantiates vlc library using specific parameters and logger class name. Also allows you to
        /// customize logging thread spawning.
        /// </summary>
        /// <param name="parameters">Parameters for vlc core.</param>
        /// <param name="loggerClassName">Name of logger to use for vlc logs.</param>
        /// <param name="spawnLoggingThread">User should pass <code>false</code> here if he doesn't want to 
        /// spawn logging thread which will integrate vlc logging into.</param>
        public LibVlcFactory(string[] parameters, string loggerClassName, bool spawnLoggingThread)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }
            if (loggerClassName == null)
            {
                throw new ArgumentNullException("loggerClassName");
            }
            if (loggerClassName.Length == 0)
            {
                throw new ArgumentException("String is empty.", "loggerClassName");
            }
            try
            {
                _vlcLibHandle = CreateLibVlcHandle(parameters);
                if (_vlcLibHandle != IntPtr.Zero)
                {
                    IntropHandleManager.LibVlcHandle = _vlcLibHandle;
                }
                else
                {
                    throw new Exception("can't create libvlc handle.");
                }
                _logger = LogManager.GetLogger(loggerClassName);
                //_vlcLog = CreateVlcLog(_logger, this);
                //if (spawnLoggingThread)
                //{
                //    InitializeAndStartLoggingThread();
                //}
            }
            catch (DllNotFoundException exc)
            {
                throw new Exception("Vlc library is not deployed. Use VlcDeployment class to deploy vlc libraries.", exc);
            }
        }

        /// <summary>
        /// Creates the instance.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns>IntPtr.</returns>
        /// <exception cref="System.ArgumentNullException">parameters</exception>
        /// <exception cref="VlcInternalException"></exception>
        public IntPtr CreateLibVlcHandle(string[] parameters)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }
            //
            var res = LibVlcCoreInterop.libvlc_new(parameters);
            if (IntPtr.Zero == res)
            {
                throw new VlcInternalException(LibVlcCoreInterop.libvlc_errmsg());
            }
            //
            return (res);
        }

        /// <summary>
        /// Creates the lib VLC media.
        /// </summary>
        /// <param name="mediaInput">The media input.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">mediaInput</exception>
        /// <exception cref="VlcInternalException"></exception>
        public BaseMedia CreateLibVlcMedia(MediaInput mediaInput)
        {
            if (mediaInput == null)
            {
                throw new ArgumentNullException("mediaInput");
            }
            //
            var mediaDescriptor = LibVlcMediaInterop.libvlc_media_new_path(_vlcLibHandle, mediaInput.Source);
            if (mediaDescriptor == IntPtr.Zero)
            {
                throw new VlcInternalException(LibVlcCoreInterop.libvlc_errmsg());
            }
            switch (mediaInput.Type)
            {
                    case MediaInputType.File:
                    return new PathMedia(mediaInput, mediaDescriptor);
                    case MediaInputType.NetworkStream:
                    return new LocationMedia(mediaInput, mediaDescriptor);
                    //TODO Need to suppor emptyMedia and UnparsedMrl
                    case MediaInputType.Empty:
                    //return new EmptyMedia();
                    break;
                    case MediaInputType.UnparsedMrl:
                    break;
            }
            return null;
        }

        /// <summary>
        /// Creates the lib VLC media.
        /// </summary>
        /// <returns>IntPtr.</returns>
        /// <exception cref="VlcInternalException"></exception>
        /// <exception cref="System.NotImplementedException"></exception>
        public IntPtr CreateLibVlcMediaPlayerHandle()
        {
            var mediaplayerDescriptor = LibVlcMediaPlayerInterop.libvlc_media_player_new(_vlcLibHandle);
            if (mediaplayerDescriptor == IntPtr.Zero)
            {
                throw new VlcInternalException(LibVlcCoreInterop.libvlc_errmsg());
            }
            //
            return mediaplayerDescriptor;
        }

        /// <summary>
        /// Creates the lib VLC media play list.
        /// </summary>
        /// <returns>IntPtr.</returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public IntPtr CreateLibVlcMediaPlayListHandle()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Creates player which can be used to control media playing.
        /// </summary>
        /// <param name="playerOutput">The player output.</param>
        /// <param name="playerType">Type of the player.</param>
        /// <returns>Player instance.</returns>
        /// <exception cref="System.ArgumentNullException">playerOutput</exception>
        public IMediaPlayer CreatePlayer(PlayerOutput playerOutput, MediaPlayerType playerType)
        {
            if (playerOutput == null)
            {
                throw new ArgumentNullException("playerOutput");
            }
            switch (playerType)
            {
                case MediaPlayerType.MediaPlayer:
                    return new VlcMediaPlayer(playerOutput, this);
                case MediaPlayerType.MediaListPlayer:
                    //return new VlcPlayer(playerOutput, _internalFactory);
                    break;
            }
            return null;
        }

        /// <summary>
        /// Creates window where player renders video. User can control position of window and it's size.
        /// </summary>
        /// <returns>Window for rendering video.</returns>
        /// <exception cref="System.InvalidOperationException">Factory for windows creation is not initialized.</exception>
        public MediaWindow CreateWindow()
        {
            if (_doubleWindowFactory == null)
            {
                throw new InvalidOperationException("Factory for windows creation is not initialized.");
            }
            return DoubleWindowFactory.CreateWindow();
        }

        ///// <summary>
        ///// Creates the VLC log.
        ///// </summary>
        ///// <param name="log">The log.</param>
        ///// <param name="logVerbosityManager">The log verbosity manager.</param>
        ///// <returns>VlcLog.</returns>
        ///// <exception cref="System.ArgumentNullException">log</exception>
        ///// <exception cref="VlcInternalException"></exception>
        //public VlcLog CreateVlcLog(ILog log, ILogVerbosityManager logVerbosityManager)
        //{
        //    if (log == null)
        //    {
        //        throw new ArgumentNullException("log");
        //    }
        //    //
        //    var libvlcLogT = LibVlcLoggingInterop.libvlc_log_open(_vlcLibHandle);
        //    if (libvlcLogT == IntPtr.Zero)
        //    {
        //        throw new VlcInternalException(LibVlcCoreInterop.libvlc_errmsg());
        //    }
        //    //
        //    return new VlcLog(libvlcLogT, logVerbosityManager, log);
        //}

        /// <summary>
        /// Gets the interop structure.
        /// </summary>
        /// <returns>libvlc_instance_t.</returns>
        public libvlc_instance_t GetInteropStructure()
        {
            return (libvlc_instance_t)Marshal.PtrToStructure(_vlcLibHandle, typeof(libvlc_instance_t));
        }

        ///// <summary>
        ///// Adds the filter request to parameters.
        ///// </summary>
        ///// <param name="parameters">The parameters.</param>
        ///// <returns>System.String[][].</returns>
        //public static string[] AddFilterRequestToParameters(string[] parameters)
        //{
        //    var parametersWithAddedFilter = new string[parameters.Length + 2];
        //    parameters.CopyTo(parametersWithAddedFilter, 0);
        //    parametersWithAddedFilter[parametersWithAddedFilter.Length - 2] = "--video-filter";
        //    parametersWithAddedFilter[parametersWithAddedFilter.Length - 1] = "adjust@elwood_adjust";
        //    //
        //    return parametersWithAddedFilter;
        //}

        /// <summary>
        /// The _double window factory
        /// </summary>
        private VlcDoubleWindowFactory _doubleWindowFactory;

        /// <summary>
        /// Factory to create vlc specific windows.
        /// </summary>
        /// <value>The double window factory.</value>
        public VlcDoubleWindowFactory DoubleWindowFactory
        {
            get
            {
                return (_doubleWindowFactory);
            }
            set
            {
                _doubleWindowFactory = value;
            }
        }

        private string _version;

        /// <summary>
        /// Version of vlc library.
        /// </summary>
        /// <value>The version.</value>
        public string Version
        {
            get
            {
                if (_version == null)
                {
                    _version = LibVlcCoreInterop.libvlc_get_version();
                }
                return (_version);
            }
        }

        #region ILogVerbosityManager members

        /// <summary>
        /// Global vlclib log verbosity level.
        /// </summary>
        /// <value>The verbosity.</value>
        public int Verbosity
        {
            get
            {
                var res = LibVlcLoggingInterop.libvlc_get_log_verbosity(_vlcLibHandle);
                return Convert.ToInt32(res);
            }
            set
            {
                LibVlcLoggingInterop.libvlc_set_log_verbosity(_vlcLibHandle, Convert.ToUInt32(value));
            }
        }

        #endregion


        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // NOTE: Leave out the finalizer altogether if this class doesn't 
        // own unmanaged resources itself, but leave the other methods
        // exactly as they are. 
        /// <summary>
        /// Finalizes an instance of the <see cref="LibVlcFactory" /> class.
        /// </summary>
        ~LibVlcFactory()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources

                // unmanaged managed resources
                Dispose(false);
                return;
            }
            
                //if (disposing)
                //{
                //    if (_thread != null)
                //    {
                //        try
                //        {
                //            if (_thread.IsAlive)
                //            {
                //                StopLoggingThread();
                //            }
                //        }
                //        catch (Exception exc)
                //        {
                //            if (_logger.IsErrorEnabled)
                //            {
                //                _logger.Error("Error while trying to interrupt a logging thread.", exc);
                //            }
                //            throw;
                //        }
                //    }
                //    if (_vlcLog != null)
                //    {
                //        _vlcLog.Dispose();
                //    }
                //}
                ////
            if (_vlcLibHandle != IntPtr.Zero)
            {
                LibVlcCoreInterop.libvlc_release(_vlcLibHandle);
            }
        }

        //#region Logging routine

        ///// <summary>
        ///// Initializes the and start logging thread.
        ///// </summary>
        //private void InitializeAndStartLoggingThread()
        //{
        //    _thread = new Thread(LoggingThreadFunc) { IsBackground = true };
        //    //
        //    _thread.Start();
        //}

        ///// <summary>
        ///// Stops the logging thread.
        ///// </summary>
        //private void StopLoggingThread()
        //{
        //    _threadStopSignalSent = true;
        //    _waitHandle.Set();
        //    //
        //    _thread.Join();
        //}

        ///// <summary>
        ///// Loggings the thread func.
        ///// </summary>
        //private void LoggingThreadFunc()
        //{
        //    while (!_threadStopSignalSent)
        //    {
        //        try
        //        {
        //            _vlcLog.UpdateMessages();
        //            _vlcLog.Clear();
        //        }
        //        catch (ObjectDisposedException)
        //        {
        //            if (_logger.IsWarnEnabled)
        //            {
        //                _logger.Warn("Logging thread : vlc logger instance has been disposed.");
        //            }
        //        }
        //        //
        //        _waitHandle.WaitOne(new TimeSpan(0, 0, 5), true);
        //    }
        //}

        //#endregion
    }
}